home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Miscellaneous / NewMaxwell / NewMaxwell.c next >
Text File  |  1994-02-03  |  21KB  |  943 lines

  1. //• Maxwell.c
  2.  
  3. //• Maxwell: particles in box, with gate in middle.  Inspired by the
  4. //• demo by the same name for the Teletype DMD 5620 terminal.
  5.  
  6. typedef struct particle 
  7. {
  8.     long x,y;                //• location.
  9.     long vx, vy;            //• velocity.
  10.     int    pict;                //• which picture this ball is.
  11. } ball;
  12.  
  13. #define    GRAD        6            //• radius for drawing (quickdraw units).
  14. #define    CRAD        28            //• radius for collision (box units).
  15. #define    SDIM        (2 * GRAD)    //• diameter on screen.
  16. #define MAXBALLS    20            //• must be even.
  17. #define    SLOW        225            //• max v squared for slow ball.
  18.  
  19. #define TWO(X) ((X)+(X))
  20. #define THREE(X) (TWO(X)+(X))
  21.  
  22. static int nslow;
  23. static void enable (MenuHandle menu, short item, short ok);
  24.  
  25. ball Balls[ MAXBALLS ];
  26. int nBalls;
  27. int GateState;                //• != 0 if gate is open.
  28. ControlHandle help;
  29.  
  30. int wide = 300;                        //• initial size of box.
  31. int tall = 255;
  32.  
  33. WindowPtr    maxwellWindow;
  34. Rect        dragRect;
  35. Rect        windowBounds = { 40, 100, 335, 430 };
  36.  
  37. MenuHandle    appleMenu, fileMenu, editMenu, widthMenu;
  38.  
  39. enum    {
  40.     appleID = 1,
  41.     fileID,
  42.     editID,
  43.     widthID
  44.     };
  45.  
  46. enum    {
  47.     openItem = 1,
  48.     closeItem,
  49.     quitItem = 4
  50.     };
  51.  
  52. SetUpWindow()
  53. {
  54.     Rect bound; 
  55.     int i;
  56.     GrafPtr saveport;
  57.  
  58.     dragRect = screenBits.bounds;
  59.  
  60.     SetRect( &bound, 40, 100, 430, 335);
  61.     maxwellWindow = NewWindow( 0L, &windowBounds, "\pMaxwell", -1, documentProc, (WindowPtr)-1L, -1, 0L );
  62.  
  63.     InitMoving();
  64.     SetPort( maxwellWindow );
  65.     AddHelpControl( maxwellWindow );
  66.     GetDateTime (&randSeed);
  67.     SetupUniverse();
  68. }
  69.  
  70. CleanUp ()
  71. {
  72.     DisposeWindow(maxwellWindow);
  73. }
  74.  
  75.  
  76. void SetUpMenus(void)
  77. {
  78.     InsertMenu(appleMenu = NewMenu(appleID, "\p\024"), 0);
  79.     InsertMenu(fileMenu = NewMenu(fileID, "\pFile"), 0);
  80.     InsertMenu(editMenu = NewMenu(editID, "\pEdit"), 0);
  81.     DrawMenuBar();
  82.     AddResMenu(appleMenu, 'DRVR');
  83.     AppendMenu(fileMenu, "\pOpen/O;Close/W;(-;Quit/Q");
  84.     AppendMenu(editMenu, "\pUndo/Z;(-;Cut/X;Copy/C;Paste/V;Clear");
  85. }
  86.  
  87. void AdjustMenus(void)
  88. {
  89.     register WindowPeek wp = (WindowPeek) FrontWindow();
  90.     short kind = wp ? wp->windowKind : 0;
  91.     Boolean DA = kind < 0;
  92.     
  93.     enable(editMenu, 1, DA);
  94.     enable(editMenu, 3, DA);
  95.     enable(editMenu, 4, DA);
  96.     enable(editMenu, 5, DA);
  97.     enable(editMenu, 6, DA);
  98.     
  99.     enable(fileMenu, openItem, !((WindowPeek) maxwellWindow)->visible);
  100.     enable(fileMenu, closeItem, DA || ((WindowPeek) maxwellWindow)->visible);
  101. }
  102.  
  103. static void enable(MenuHandle menu, short item, short ok)
  104. {
  105.     if (ok)
  106.         EnableItem(menu, item);
  107.     else
  108.         DisableItem(menu, item);
  109. }
  110.  
  111. void HandleMenu (long mSelect)
  112. {
  113.     int            menuID = HiWord(mSelect);
  114.     int            menuItem = LoWord(mSelect);
  115.     Str255        name;
  116.     GrafPtr        savePort;
  117.     WindowPeek    frontWindow;
  118.     
  119.     switch (menuID)
  120.     {
  121.         case    appleID:
  122.             GetPort(&savePort);
  123.             GetItem(appleMenu, menuItem, name);
  124.             OpenDeskAcc(name);
  125.             SetPort(savePort);
  126.         break;
  127.     
  128.         case    fileID:
  129.             switch (menuItem)
  130.             {
  131.                 case    openItem:
  132.                     ShowWindow(maxwellWindow);
  133.                     SelectWindow(maxwellWindow);
  134.                 break;
  135.                                   
  136.                 case    closeItem:
  137.                     if ((frontWindow = (WindowPeek) FrontWindow()) == 0L)
  138.                 break;
  139.                 
  140.                 if (frontWindow->windowKind < 0)
  141.                     CloseDeskAcc(frontWindow->windowKind);
  142.                 else 
  143.                     if ((frontWindow = (WindowPeek) maxwellWindow) != NULL)
  144.                             HideWindow(maxwellWindow);
  145.                   break;
  146.                               
  147.                 case    quitItem:
  148.                     ExitToShell();
  149.                 break;
  150.             }
  151.         break;
  152.                   
  153.         case    editID:
  154.             if (!SystemEdit(menuItem-1))
  155.                 SysBeep(5);
  156.         break;
  157.     }
  158. }
  159.  
  160. //• StepBall() moves all the balls
  161. StepBall(GrafPtr wp, int infront)
  162. {
  163.     register int i,j;
  164.  
  165.     nslow = 0;
  166.     SortBalls();
  167.     for ( i = 0; i < nBalls; i++ ) 
  168.     {
  169.         for ( j = i+1; j < nBalls; j++ )
  170.             if ( BallCollide( Balls+i, Balls+j ) )
  171.                 break;
  172.         WallCollide( Balls+i );
  173.         if ( infront )
  174.             SetTheGate( Button() );
  175.     }
  176.     //• If we just draw the balls in order on the screen it will look
  177.     //• bad, since we have them sorted by x.
  178.     for ( i = 0; i < 10; i++ )
  179.     for ( j = i; j < nBalls; j += 10 )
  180.         MoveBall( Balls + j, wp );
  181.  
  182.     if ( nslow < nBalls/3 )
  183.         WallForces( -1 );
  184.     else
  185.     if ( nslow >= (nBalls/3)<<1 )
  186.         WallForces( 1 );
  187.     else
  188.         WallForces( 0 );
  189. }
  190.  
  191. MoveBall(ball *pA, GrafPtr wp)
  192. {
  193.     Rect R;
  194.     register long v2;
  195.     ball before;
  196.     
  197.     before = *pA;        //• save old ball.
  198.     ComputeBall( pA );        //• compute new ball.
  199.     
  200.     v2 = pA->vx * pA->vx + pA->vy * pA->vy;
  201.     
  202.     if ( v2 <= SLOW ) nslow++;
  203.     
  204.     pA->pict = (v2 <= SLOW ? 0 : 1);
  205.     
  206.     Draw( wp, &before, pA );
  207.     
  208.     return;
  209. }
  210.  
  211. //• Redraw() is called to deal with update events in our window.
  212.  
  213. Redraw(GrafPtr wp)
  214. {
  215.     Rect bound; int i;
  216.  
  217.     BeginUpdate( wp );
  218.     SetRect( &bound, 0, 0, wide+30, tall+40 );
  219.     EraseRect( &bound );                //• clear whole window.
  220.     SetRect( &bound, 15, 25, 15+wide, 25+tall );
  221.     FrameRect( &bound );                //• draw outer border.
  222.     SetRect( &bound, 13+wide/2, 25, 17+wide/2, 25+tall );
  223.     InvertRect( &bound );                //• draw barrier.
  224.     SetRect( &bound, 13+wide/2, 24+tall/3, 17+wide/2, 26+(tall+tall)/3 );
  225.     InvertRect( &bound );                //• remove gate + 1 pixel.
  226.     Toggle();
  227.     GateState = 0;                        //• gate is closed.
  228.     DrawControls( wp );
  229.     
  230.     //• Now draw the balls.  This only works for an even number 
  231.     //• of balls,since Draw() wants two balls.
  232.     for ( i = 0; i < nBalls; i+=2 )
  233.         Draw( wp, Balls + i, Balls + i + 1 );
  234.  
  235.     EndUpdate( wp );
  236.  
  237.     //• Now do the grow icon.
  238.     SetRect( &bound, wide+15, tall+25, wide+30, tall+40 );
  239.     ClipRect( &bound );
  240.     DrawGrowIcon( wp );
  241.     SetRect( &bound, 0, 0, 31415, 27182 );
  242.     ClipRect( &bound );
  243. }
  244.  
  245. //• Draw() draws two balls in the window.  It is used both to draw the
  246. //• initial balls, with the two balls being different balls, and to
  247. //• draw a ball that has moved.  In the latter case, the two balls are
  248. //• the same ball, once at the old position and once at the new postion.
  249. //• This works since drawing is done in xor mode.
  250. Draw(GrafPtr wp, ball *pA, ball *pB)
  251. {
  252.     register long ax, ay, bx, by;
  253.     
  254.     ax = pA->x >> 2;
  255.     ay = pA->y >> 2;
  256.     
  257.     bx = pB->x >> 2;
  258.     by = pB->y >> 2;
  259.     
  260.     MoveBits( wp, bx-GRAD + 15, by-GRAD + 25, ax-GRAD + 15,
  261.         ay-GRAD + 25, (long)pB->pict, (long)pA->pict );
  262. }
  263.  
  264. //• set gate to a given state.  bs != 0 means make sure the gate is
  265. //• open, and bs == 0 means make sure it is closed.
  266. SetTheGate( bs ) 
  267. {
  268.     if ( !!bs != GateState ) 
  269.     {
  270.         GateState = !!bs;
  271.         Toggle();
  272.     }
  273. }
  274.  
  275. //• Change the state of the gate on the screen
  276. Toggle() 
  277. {
  278.     Rect bound;
  279.  
  280.     SetRect( &bound, 13 + wide / 2, 
  281.                      25 + tall / 3, 
  282.                      17 + wide / 2, 
  283.                      25 + (tall + tall) / 3 );
  284.     InvertRect( &bound );
  285. }
  286.  
  287. //• Generate a random integer in [low,high].  If "contract" is not zero,
  288. //• it skews the distribution to favor numbers nearer the center of
  289. //• the interval
  290.  
  291. rani(low,high,contract)
  292.     int low, high;
  293. {
  294.     register long r;
  295.     register int range;
  296.     
  297.     r = (Random()>>1) + 16384;
  298.     if ( !contract )
  299.         return r * (high-low) / 32768L + low;
  300.     
  301.     range = (high - low) >> 1;
  302.     
  303.     r = r * range;
  304.     r /= 32768;
  305.     r *= Random();
  306.     range = r / 32768;
  307.     
  308.     return ( (low + high) >> 1 ) + range;
  309. }
  310.  
  311. //• Set up balls.
  312.  
  313. SetupUniverse() 
  314. {
  315.     int i; long nb;
  316.     
  317.     PenNormal();
  318.     PenMode( patXor );
  319.     
  320.     nb = (long)tall * (long)wide; nb >>= 13; nb <<= 1;
  321.     nBalls = nb + 4;
  322.     if ( nBalls > MAXBALLS )
  323.         nBalls = MAXBALLS;
  324.  
  325.     for ( i = 0; i < nBalls; i++ ) 
  326.     {
  327.         register long a,b;
  328.         
  329.         Balls[i].x  = rani(CRAD, (wide<<2)-CRAD, 0);
  330.         Balls[i].y  = rani(CRAD, (tall<<2)-CRAD, 0);
  331.         Balls[i].vx = a = rani(-40, 40, 1);
  332.         Balls[i].vy = b = rani(-40, 40, 1);
  333.         if ( a*a + b*b <= SLOW )
  334.             Balls[i].pict = 0;
  335.         else
  336.             Balls[i].pict = 1;
  337.     }
  338. }
  339.  
  340.  
  341. //• ReSize the window
  342.  
  343. ReSize(WindowPtr wp, Point *mp)
  344. {
  345.     Rect sizeRect; long result;
  346.     
  347.     SetRect( &sizeRect, 150, 80, 2500, 402 );
  348.     result = GrowWindow( wp, *mp, &sizeRect );
  349.     if ( !result )
  350.         return;
  351.     tall = result >> 16; tall &= 0xffff;
  352.     wide = result & 0xffff;
  353.     
  354.     wide -= 29; wide -= wide % 2;
  355.     tall -= 38; tall -= tall % 3;
  356.     
  357.     DisposeControl( help );
  358.     
  359.     SizeWindow( wp, wide + 30, tall + 40, 0 );
  360.     
  361.     AddHelpControl( wp );
  362.     
  363.     SetRect( &sizeRect, 0, 0, wide + 30, tall + 40 );
  364.     InvalRect( &sizeRect );
  365.     
  366.     SetupUniverse();
  367.     Redraw( wp );
  368. }
  369.  
  370.  
  371. //• Sort balls by x co-ordinate.  The first time this is called, it has
  372. //• to do a lot of work, but on subsequent calls, the balls will for the
  373. //• most part be already in order.  We shall use a bubble sort, since
  374. //• it is very fast for an already sorted list.
  375.  
  376. SortBalls() 
  377. {
  378.     register int i, j, flag;
  379.     
  380.     flag = 1;
  381.     for ( i = 0; i < (nBalls - 1) && flag; i++ )
  382.         for ( flag = 0, j = nBalls-1; j > i; --j )
  383.             if ( Balls[j-1].x > Balls[j].x ) 
  384.             {
  385.                 ball temp;
  386.                 flag = 1;
  387.                 temp = Balls[j-1]; Balls[j-1] = Balls[j];
  388.                 Balls[j] = temp;
  389.             }
  390. }
  391.  
  392.  
  393. //• repond to a click in the help button
  394.  
  395. ShowHelp() 
  396. {
  397.     Rect bound;
  398.     WindowPtr wp;
  399.     char *h1, *h2, *h3, *h4;
  400.     char helptext[1024];
  401.     
  402.     SetRect( &bound, 91, 68, 421, 303 );
  403.     wp = NewWindow( 0L, &bound, "\pA", -1, dBoxProc, (WindowPtr)-1L, 0, 0L );
  404.     SetPort( wp );
  405.  
  406.     TextMode( srcXor );
  407.     TextSize( 9 );
  408.     
  409.     SetRect( &bound, 5, 5, 325, 230 );
  410.  
  411.     h1 = "\
  412. Maxwell V2.1 from Mithral Engineering.\r\
  413. This program and its source code are in the public domain.\r\r\
  414. Whenever the Maxwell window is in front, holding down the\
  415.  mouse button opens the gate so that balls may go from one\
  416.  side to the other.\r\r";
  417.      h2 = "\
  418. Try to get all the fast balls ( the black ones ) in the right\
  419.  half of the window, and all the slow ones in the left.\r\r\
  420. Due to roundoff errors, at each collision there is a slight";
  421.     h3 = "\
  422.  net decrease in the total energy of the balls.  To balance\
  423.  this, the right wall will become 'hot' if less than one third\
  424.  of the balls are fast balls.  When a slow ball hits the hot right";
  425.      h4 = "\
  426.  wall, it will become a very fast ball.  When more than one third\
  427.  of the balls are fast, the right wall will cool off.\r\
  428. --------------------------------------------------------------\r\
  429. Made to run under THINK C™ 5.0.4 by Kenneth A. Long on 3 Feb 94.\r\
  430. The shell is basically \"Bullseye,\" and all merged into one .c file.";
  431.  
  432.     strcpy( helptext, h1 );
  433.     strcat( helptext, h2 );
  434.     strcat( helptext, h3 );
  435.     strcat( helptext, h4 );
  436.  
  437.     TextBox( helptext, (long)strlen( helptext ), &bound, teJustLeft );
  438.     
  439.     while ( !Button() )
  440.         ;
  441.     FlushEvents( mDownMask, 0 );
  442.     DisposeWindow( wp );
  443. }
  444.  
  445.  
  446. //• put up the help button
  447.  
  448. AddHelpControl(WindowPtr wp)
  449. {
  450.     Rect bound;
  451.     
  452.     SetRect( &bound, (wide>>1)-7, 4 , (wide>>1) + 37, 22 );
  453.     
  454.     help = NewControl( wp, &bound, "\pHelp!", -1,0,0,0, pushButProc, 0L );
  455. }
  456.  
  457.  
  458. //• find out if the user wants help
  459.  
  460. CheckHelp(EventRecord *erp, WindowPtr win)
  461. {
  462.     int part;
  463.     ControlHandle ch;
  464.     
  465.     part = FindControl( erp->where, win, &ch );
  466.     
  467.     if ( part != inButton || ch != help )
  468.         return;
  469.         
  470.      part = TrackControl( ch, erp->where, 0L );
  471.     
  472.      if ( part == inButton )
  473.          ShowHelp();
  474. }
  475.  
  476.  
  477.  
  478. //• move or draw ball on screen.
  479.  
  480. //• There are three off-screen bitmaps.  The first two hold the images of
  481. //• a slow ball and a fast ball.  The third is used for combining the
  482. //• before and after pictures so that a screen update of a moving ball
  483. //• whose new postion overlaps its old position can be done in one
  484. //• screen operation.  This is intended to reduce flicker, by cutting
  485. //• number of copybits calls to the screen from two to one per slow
  486. //• moving ball.  However, it increases the total number of copybits
  487. //• calls, which makes things run slower.
  488.  
  489. //• The use of this third bitmap is enabled by defining SMOOTH.  It will
  490. //• run ~25% slower with SMOOTH defined.
  491.  
  492.  
  493. char slowBits[ 2 * (SDIM) * ((SDIM + 15)/16) ];        //• slow bits.
  494. char fastBits[ 2 * (SDIM) * ((SDIM + 15)/16) ];        //• fast bits.
  495.  
  496. #ifdef SMOOTH
  497. //• combined bits.
  498. char combBits[ 2 * (2 * SDIM) * ((2 * SDIM + 15) / 16) ];    
  499. #endif
  500.  
  501. BitMap slowMap, fastMap            //• bitmaps for slow and fast bits.
  502. #ifdef SMOOTH
  503.     ,combMap                    //• and combined bits.
  504. #endif
  505. ;
  506.  
  507. //• MoveBits() draws two balls.  The first is in the square with corner
  508. //• at Sx, Sy, and side SDIM.  The second is at Dx and Dy, and has the
  509. //• same size.  Which drawing to use for each is determined by op and 
  510. //• np.
  511.  
  512. MoveBits(GrafPtr win, long Sx, long Sy, long Dx, long Dy, 
  513.                     long op, long np)
  514. {
  515.     register int sx, sy, dx, dy;
  516.     int tx,ty;
  517.     int rt, rl;
  518.     Rect S,D;
  519.  
  520.     //• The pointers to the bits in the bitmaps may have changed if we
  521.     //• have been moved on the heap since we were initialized, so 
  522.     //• we shall fix them.
  523.     slowMap.baseAddr = slowBits;
  524.     fastMap.baseAddr = fastBits;
  525. #ifdef SMOOTH
  526.     combMap.baseAddr = combBits;
  527. #endif
  528.     
  529.     //• convert to integers.
  530.     sx = Sx; 
  531.     sy = Sy; 
  532.     dx = Dx; 
  533.     dy = Dy; 
  534.  
  535.     tx = dx - sx;            //• relative x positions.
  536.     ty = dy - sy;            //• relative y positions.
  537.     
  538.     
  539. #define pict(v) ((v) == 0 ? &slowMap : &fastMap )
  540.  
  541.     SetPort( win );
  542.  
  543. //• If the balls do not overlap, or if SMOOTH is not defined, then we
  544. //• simply want to erase the first ball and draw the second.
  545. #ifdef SMOOTH
  546.     //• If the balls can't possibly overlap, then just draw them 
  547.     //• directly on the screen
  548.     if ( tx < - GRAD || tx > GRAD || ty < - GRAD || ty > GRAD ) 
  549.     {
  550. #endif
  551.         SetRect( &S, 0, 0, SDIM, SDIM );
  552.         SetRect( &D, sx, sy, sx + SDIM, sy + SDIM );
  553.         CopyBits( pict(op), &win->portBits, &S, &D, srcXor, 0L );
  554.         SetRect( &D, dx, dy, dx + SDIM, dy + SDIM );
  555.         CopyBits( pict(np), &win->portBits, &S, &D, srcXor, 0L );
  556.         return;
  557. #ifdef SMOOTH
  558.     }
  559.     
  560.     //• The balls are close enough that we may combine their 
  561.     //• updates into one CopyBits to the screen.
  562.     
  563.     //• The rest of this is a pain to explain without a diagram,
  564.     //• so figure it out for yourself!
  565.     if ( ty > 0 )
  566.         if ( tx > 0 )    
  567.         { 
  568.             rt = 0;    
  569.             rl = 0; 
  570.         }
  571.         else            
  572.             { 
  573.                 rt = 0;    
  574.                 rl = SDIM; 
  575.         }
  576.     else
  577.         if ( tx > 0 )    
  578.         { 
  579.             rt = SDIM; 
  580.             rl = 0; 
  581.         }
  582.         else            
  583.             { 
  584.                 rt = SDIM; 
  585.                 rl = SDIM; 
  586.         }
  587.         
  588.     SetRect( &D, 0, 0, 2 * SDIM, 2 * SDIM );
  589.     CopyBits( &combMap, &combMap, &D, &D, srcXor, 0L );
  590.     SetRect( &S, 0, 0, SDIM, SDIM );
  591.     SetRect( &D, rl, rt, rl+SDIM, rt+SDIM );
  592.     CopyBits( pict(op), &combMap, &S, &D, srcCopy, 0L );
  593.     SetRect( &D, rl + tx, rt + ty, rl + tx + SDIM, rt + ty + SDIM );
  594.     CopyBits( pict(np), &combMap, &S, &D, srcXor, 0L );
  595.     SetRect( &S, 0, 0, SDIM * 2, SDIM * 2 );
  596.     SetRect( &D, sx - rl, sy - rt, sx - rl + 2 * SDIM, sy - rt + 2 * SDIM );
  597.     CopyBits( &combMap, &win->portBits, &S, &D, srcXor, 0L );
  598. #endif
  599. }
  600.  
  601. //• Initialize the bitmaps
  602.  
  603. InitMoving() 
  604. {        
  605.     Rect S,D;
  606.     GrafPort port;
  607.  
  608.     //• Get a grafport to do our thing in.
  609.     OpenPort( &port );
  610.     PenNormal();
  611.     
  612.     //• Bitmap for slow bits...
  613.     slowMap.baseAddr = slowBits;
  614.     slowMap.rowBytes = 2*((SDIM+15)/16);
  615.     slowMap.bounds.top = 0;
  616.     slowMap.bounds.bottom = 2*SDIM;
  617.     slowMap.bounds.left = 0;
  618.     slowMap.bounds.right = 2*SDIM;
  619.     
  620.     //• Bitmap for fast bits...
  621.     fastMap.baseAddr = fastBits;
  622.     fastMap.rowBytes = 2*((SDIM+15)/16);
  623.     fastMap.bounds.top = 0;
  624.     fastMap.bounds.bottom = 2*SDIM;
  625.     fastMap.bounds.left = 0;
  626.     fastMap.bounds.right = 2*SDIM;
  627.     
  628.     SetPortBits( &slowMap );        //• prepare to draw slow ball.
  629.     
  630.     SetRect( &S, 0, 0, SDIM, SDIM );
  631.     FrameOval( &S );
  632.  
  633.     //• ...risking the taste-police - :)
  634.     MoveTo( 3, 7 ); 
  635.     LineTo( 4, 8 );    
  636.     LineTo( 7, 8 ); 
  637.     LineTo( 8, 7 );
  638.     MoveTo( 4, 4 ); 
  639.     LineTo( 4, 4 );
  640.     MoveTo( 7, 4 ); 
  641.     LineTo( 7, 4 );
  642.  
  643.     SetPortBits( &fastMap );        //• prepare to draw fast ball.
  644.     InvertOval( &S );
  645.     
  646. #ifdef SMOOTH
  647.     //• Bitmap for combined drawings...
  648.     combMap.baseAddr = combBits;
  649.     combMap.rowBytes = 2*((SDIM+SDIM+15)/16);
  650.     combMap.bounds.top = 0;
  651.     combMap.bounds.bottom = 2*SDIM;
  652.     combMap.bounds.left = 0;
  653.     combMap.bounds.right = 2*SDIM;
  654. #endif    
  655. }
  656.  
  657.  
  658. //• Do collisions and movement.
  659.  
  660. //• BallCollide() deals with a possible collision between two 
  661. //• specific balls.
  662. //• The first ball will not be to the right of the second ball.  
  663. //• Returns one if the second ball is far enough to the right so 
  664. //• that no balls farther right could collide with the first ball, 
  665. //• else returns zero.
  666.  
  667. BallCollide(ball *pA, ball *pB)
  668. {
  669.     register long k;
  670.     long tAvx, tAvy;
  671.     register long tBx, tBy;
  672.     long tBvx, tBvy;
  673.     long WIDE, TALL;
  674.     
  675.     WIDE = wide<<2;            //• scale from window to physical co-ords.
  676.     TALL = tall<<2;
  677.     
  678.     //• Deal with the barrier and the gate.
  679.     if ( TWO(pA->x) <= WIDE && TWO(pB->x) >= WIDE )
  680.         //• gate closed means no collision.
  681.         if ( ! GateState )
  682.             return 0;
  683.         else
  684.             //• If either ball is below gate, no collision.
  685.             if ( THREE(pA->y) < TALL || THREE(pB->y) < TALL )
  686.                 return 0;
  687.         else
  688.             //• If either ball is above gate, no collision.
  689.             if (THREE(pA->y) > 
  690.                 TWO(TALL) || 
  691.                 THREE(pB->y) > 
  692.                 TWO(TALL) )
  693.             return 0;
  694.  
  695.     //• Shift to A's co-ordinate system.
  696.     tBx = pB->x - pA->x;
  697.     tBy = pB->y - pA->y;
  698.     
  699.     tBvx = pB->vx - pA->vx;    
  700.     tAvx = 0;
  701.     
  702.     tBvy = pB->vy - pA->vy;    
  703.     tAvy = 0;
  704.     
  705.     //• See if the balls are close enough to have collided.
  706.     if ( tBx > TWO(CRAD) )
  707.         return 1;
  708.     if ( tBx * tBx + tBy * tBy > (CRAD * CRAD<<2) )
  709.         return 0;
  710.     
  711.     k = tBx * tBvx + tBy * tBvy;
  712.     
  713.     //• Make sure they are going towards each other.
  714.     if ( k > -1 )
  715.         return 0;
  716.         
  717.     k = ( tBy * tBvx - tBx * tBvy ) / k;
  718.     
  719.     tAvx = ( tBvx - k * tBvy ) / ( 1 + k*k );
  720.     tAvy = ( k * tBvx + tBvy ) / ( 1 + k*k );
  721.     
  722.     tBvx =  k * tAvy;
  723.     tBvy = -k * tAvx;
  724.     
  725.     pB->vx = pA->vx + tBvx;
  726.     pB->vy = pA->vy + tBvy;
  727.     pA->vx += tAvx;
  728.     pA->vy += tAvy;
  729.     
  730.     return 0;
  731. }
  732.  
  733. //• Because the calculations above use longs instead of floats, we have a
  734. //• lot of round off error.  This seems to manifest itself by causing the
  735. //• balls to slow down over time.  We use the walls to correct this.
  736.  
  737. //• If "we" is greater than zero, we are attempting to add energy to the
  738. //• system.  We do this by looking for slow balls bouncing off the right
  739. //• wall.  When we find such a ball, we give it a swift kick towards the
  740. //• left.
  741.  
  742. //• If "we" is less than zero, then we are trying to remove energt from
  743. //• the system for some reason.  In this case, the outer walls become
  744. //• slightly sticky, with the ball slowing down by abs(we) perpendicular
  745. //• to the wall.
  746.  
  747. //• This stuff is done in WallCollide().
  748.  
  749. static int we = 0;            //• wall energy factor.
  750.  
  751. WallForces( i ) 
  752. {
  753.     we = i;
  754. }
  755.  
  756.  
  757. //• WallCollide() checks for collisions between a ball walls or the gate.
  758. WallCollide(ball *pA)
  759. {
  760.     register long WIDE, TALL;
  761.     
  762.     WIDE = wide<<2; TALL = tall<<2;
  763.     
  764.     if ( (pA->x <= CRAD && pA->vx < 0)
  765.       || (pA->x >= WIDE-CRAD && pA->vx > 0) ) 
  766.     {
  767.         pA->vx = -pA->vx;
  768.         if ( we > 0 ) 
  769.         {
  770.             if ( pA->vx < 0 && pA->pict == 0 && pA->x >= WIDE-CRAD)
  771.                 pA->vx -= 30;                //• swift kick.
  772.         } 
  773.         else 
  774.             {
  775.                 if ( pA->vx > 0 )
  776.                     pA->vx += we;
  777.                 else
  778.                     pA->vx -= we;
  779.         }
  780.     }
  781.     if ((pA->y <= CRAD && pA->vy < 0) ||
  782.         (pA->y >= TALL-CRAD && pA->vy > 0) ) 
  783.     {
  784.         pA->vy = -pA->vy;
  785.         if ( we < 0 ) 
  786.         {
  787.             if ( pA->vy > 0 )
  788.                 pA->vy += we;
  789.             else
  790.                 pA->vy -= we;
  791.         }
  792.  
  793.     }
  794.     
  795.     //• if the ball is on the same level as the gate, and the gate 
  796.     //• is open, there is nothing for the ball to hit on the barrier.
  797.  
  798.     if ( TALL < THREE(pA->y) && THREE(pA->y) < TWO(TALL) && GateState )
  799.         return;
  800.         
  801.     WIDE >>= 1;            //• location of the barrier.
  802.  
  803.     //• see if the ball hits the barrier.
  804.     if ( pA->x <= WIDE && pA->vx > 0 ) 
  805.     {
  806.         if ( pA->x + CRAD >= WIDE || pA->x + pA->vx > WIDE )
  807.             pA->vx = -pA->vx;
  808.     } 
  809.     else
  810.         if ( pA->x >= WIDE &&  pA->vx < 0 )
  811.             if ( pA->x - CRAD <= WIDE || pA->x + pA->vx < WIDE )
  812.                 pA->vx = -pA->vx;
  813. }
  814.  
  815. ComputeBall(ball *pA)
  816. {
  817.     register long vx, vy;
  818.     pA->x += vx = pA->vx;
  819.     pA->y += vy = pA->vy;
  820.  
  821.     
  822.     //• check for stalled balls, and offer them a chance to get going again.
  823.     if ( vx == 0 && vy == 0 ) 
  824.     {
  825.         if ( Random() > 0 )
  826.             pA->vx = Random() > 0 ? 1 : -1;
  827.         if ( Random() > 0 )
  828.             pA->vy = Random() > 0 ? 1 : -1;
  829.     }
  830. }
  831.  
  832. void InitMacintosh(void)
  833. {
  834.     MaxApplZone();
  835.     
  836.     InitGraf(&thePort);
  837.     InitFonts();
  838.     FlushEvents(everyEvent, 0);
  839.     InitWindows();
  840.     InitMenus();
  841.     TEInit();
  842.     InitDialogs(0L);
  843.     InitCursor();
  844. }
  845.  
  846. void HandleMouseDown (EventRecord    *theEvent)
  847. {
  848.     WindowPtr    theWindow;
  849.     int            windowCode = FindWindow (theEvent->where, &theWindow);
  850.     int infront;
  851.     
  852.     switch (windowCode)
  853.     {
  854.         case inSysWindow: 
  855.             SystemClick (theEvent, theWindow);
  856.         break;
  857.             
  858.         case inMenuBar:
  859.             AdjustMenus();
  860.             HandleMenu(MenuSelect(theEvent->where));
  861.         break;
  862.             
  863.         case inDrag:
  864.             if (theWindow == maxwellWindow)
  865.             DragWindow(maxwellWindow, theEvent->where, &dragRect);
  866.         break;
  867.             
  868.         case inContent:
  869.             if (theWindow == maxwellWindow)
  870.             {                
  871.                     GlobalToLocal( &theEvent->where );
  872.                     
  873.                     //• check grow icon...
  874.                     if ( 15 + wide < theEvent->where.h
  875.                         && theEvent->where.h < 30 + wide
  876.                         && 25 + tall < theEvent->where.v &&
  877.                         theEvent->where.v < 40 + tall) 
  878.                     {
  879.                         LocalToGlobal( &theEvent->where );
  880.                         ReSize( maxwellWindow, &theEvent->where );
  881.                     } 
  882.                     else
  883.                         //• see if there is any reason to check the 
  884.                         //• help button...
  885.                         if ( theEvent->where.v <= 25 )
  886.                             //• yup, there is...
  887.                             CheckHelp( theEvent, maxwellWindow );
  888.             }
  889.         break;
  890.             
  891.         case inGoAway:
  892.             if (theWindow == maxwellWindow && 
  893.                 TrackGoAway(maxwellWindow, theEvent->where))
  894.                 HideWindow(maxwellWindow);
  895.         break;
  896.     }
  897. }
  898.  
  899. void HandleEvent(void)
  900. {
  901.     int            ok, infront;
  902.     EventRecord    theEvent;
  903.  
  904.     HiliteMenu(0);
  905.     SystemTask ();        /* Handle desk accessories */
  906.  
  907.     StepBall( maxwellWindow, infront );
  908.  
  909.     ok = GetNextEvent (everyEvent, &theEvent);
  910.     if (ok)
  911.     switch (theEvent.what)
  912.     {
  913.         case mouseDown:
  914.             HandleMouseDown(&theEvent);
  915.         break;
  916.             
  917.         case keyDown: 
  918.         case autoKey:
  919.             if ((theEvent.modifiers & cmdKey) != 0)
  920.             {
  921.                 AdjustMenus();
  922.                 HandleMenu(MenuKey((char) (theEvent.message & charCodeMask)));
  923.             }
  924.         break;
  925.             
  926.         case activateEvt:
  927.         case updateEvt:
  928.             SetTheGate(0);
  929.             Redraw( maxwellWindow );
  930.             HiliteControl( help, infront ? 0 : 255 );
  931.         break;
  932.     }
  933. }
  934.  
  935. void main( void)
  936. {
  937.     InitMacintosh();
  938.     SetUpMenus();
  939.     SetUpWindow();
  940.     for (;;)
  941.         HandleEvent();
  942. }
  943.